﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using DXBase;
using System.Runtime.InteropServices;

namespace DXBase.Structs
{
	/// <summary>
	/// This class is used to represent affine transformation of 2D space.
	/// However it is prone to cumulative numerical error, hence it is usually 
	/// only used to pass the final transform to DirectX
	/// </summary>
	[StructLayout(LayoutKind.Sequential)]
	public struct Matrix3x2F
	{
		// this struct has the same layout as DXBase.float3x2
		public float m11, m12;
		public float m21, m22;
		public float m31, m32;

		public Matrix3x2F(float m11, float m12, float m21, float m22, float m31, float m32)
		{
			this.m11 = m11;
			this.m12 = m12;
			this.m21 = m21;
			this.m22 = m22;
			this.m31 = m31;
			this.m32 = m32;
		}

		unsafe public static implicit operator Matrix3x2F(float3x2 v)
		{
			Matrix3x2F dst;
			*&dst = *(Matrix3x2F*)&v;
			return dst;
		}
		unsafe public static implicit operator float3x2(Matrix3x2F v)
		{
			float3x2 dst;
			*&dst = *(float3x2*)&v;
			return dst;
		}

		unsafe public float this[int row, int col]
		{
			get
			{
				if (row < 0 || col < 0 || row >= 3 || col >= 2)
					throw new ArgumentOutOfRangeException();
				fixed (float* p = &m11)
					return p[row * 2 + col];
			}
			set
			{
				if (row < 0 || col < 0 || row >= 3 || col >= 2)
					throw new ArgumentOutOfRangeException();
				fixed (float* p = &m11)
					p[row * 2 + col] = value;
			}
		}

		public static Matrix3x2F Identity() 
		{
			return new Matrix3x2F(
				1, 0,
				0, 1,
				0, 0
				);
		}
		public static Matrix3x2F Rotation(float degrees) { return Rotation(degrees, new Vector2F()); }
		public static Matrix3x2F Rotation(float degrees, Vector2F center)
		{
			float cosa = degrees.DegToRad().Cosf();
			float sina = degrees.DegToRad().Sinf();
			return new Matrix3x2F(
				cosa, sina,
				-sina, cosa,
				- cosa * center.X + sina * center.Y + center.X, - sina * center.X - cosa * center.Y + center.Y
				);
		}
		public static Matrix3x2F Scale(float sx, float sy) { return Scale(sx, sy, new Vector2F()); }
		public static Matrix3x2F Scale(float sx, float sy, Vector2F center)
		{
			return new Matrix3x2F(
				sx, 0,
				0, sy, 
				center.X * (1 - sx), center.Y * (1 - sy)
				);
		}
		public static Matrix3x2F Skew(float degreesX, float degreesY) { return Skew(degreesX, degreesY, new Vector2F()); }
		public static Matrix3x2F Skew(float degreesX, float degreesY, Vector2F center)
		{
			float skewx = degreesX.DegToRad().Tanf();
			float skewy = degreesY.DegToRad().Tanf();
			return new Matrix3x2F(
				1, skewy,
				skewx, 1, 
				- skewx * center.Y, - skewy * center.X
				);
		}
		public static Matrix3x2F Translate(Vector2F p) { return Translate(p.X, p.Y); }
		public static Matrix3x2F Translate(float dx, float dy)
		{
			return new Matrix3x2F(
				1, 0,
				0, 1, 
				dx, dy
				);
		}

		public static Matrix3x2F operator *(Matrix3x2F a, Matrix3x2F b)
		{
			return new Matrix3x2F(
				a.m11 * b.m11 + a.m12 * b.m21, a.m11 * b.m12 + a.m12 * b.m22,
				a.m21 * b.m11 + a.m22 * b.m21, a.m21 * b.m12 + a.m22 * b.m22, 
				a.m31 * b.m11 + a.m32 * b.m21 + b.m31, a.m31 * b.m12 + a.m32 * b.m22 + b.m32
				);
		}
		public static Vector2F operator *(Matrix3x2F m, Vector2F p)
		{
			return new Vector2F(
				m.m11 * p.X + m.m21 * p.Y + m.m31,
				m.m12 * p.X + m.m22 * p.Y + m.m32
				);
		}
		public Matrix3x2F Invert(Matrix3x2F m)
		{
			float det = m.m11 * m.m22 - m.m12 * m.m21;
			Matrix3x2F mi;
			mi.m11 = m.m22 / det;
			mi.m12 = -m.m12 / det;
			mi.m21 = -m.m21 / det;
			mi.m22 = m.m11 / det;
			mi.m31 = - mi.m11 * m.m31 - mi.m21 * m.m32;
			mi.m32 = - mi.m12 * m.m31 - mi.m22 * m.m32;
			return mi;
		}

		public override string ToString()
		{
			var sb = new StringBuilder();
			for (int i = 0; i < 2; i++)
			{
				if (i == 0) sb.Append('[');
				else sb.AppendLine().Append(' ');
				for (int j = 0; j < 3; j++)
				{
					if (j > 0) sb.Append(", ");
					sb.Append(this[i, j]);
				}
			}
			sb.Append(']');
			return sb.ToString();
		}
	}
}
